# -*- coding: utf-8 -*-

"""
Copied from standard lib pprint code, modify dict output style, support OrderedDict
made it's better for human reading

pformat()
    Format a Python obj into a pretty-printed representation.
"""

import sys as _sys
import warnings
from collections import OrderedDict

try:
    from cStringIO import StringIO as _StringIO
except ImportError:
    from StringIO import StringIO as _StringIO

# cache these for faster access:
_commajoin = ", ".join
_id = id
_len = len
_type = type


def cprint(obj, stream=None, indent=1, width=80, depth=None):
    """Pretty-print a Python obj to a stream [default is sys.stdout]."""
    printer = CPrettyPrinter(
        stream=stream, indent=indent, width=width, depth=depth)
    printer.cprint(obj)


def cformat(obj, indent=1, width=80, depth=None):
    """Format a Python obj into a pretty-printed representation."""
    return CPrettyPrinter(indent=indent, width=width, depth=depth).cformat(obj)


def _sorted(iterable):
    with warnings.catch_warnings():
        if _sys.py3kwarning:
            warnings.filterwarnings("ignore", "comparing unequal types "
                                              "not supported", DeprecationWarning)
        return sorted(iterable)


class CPrettyPrinter:
    def __init__(self, indent=1, width=80, depth=None, stream=None):
        """Handle pretty printing operations onto a stream using a set of
        configured parameters.

        indent
            Number of spaces to indent for each level of nesting.

        width
            Attempted maximum number of columns in the output.

        depth
            The maximum depth to print out nested structures.

        stream
            The desired output stream.  If omitted (or false), the standard
            output stream available at construction will be used.
        """
        indent = int(indent)
        width = int(width)
        assert indent >= 0, "indent must be >= 0"
        assert depth is None or depth > 0, "depth must be > 0"
        assert width, "width must be != 0"
        self._depth = depth
        self._indent_per_level = indent
        self._width = width
        if stream is not None:
            self._stream = stream
        else:
            self._stream = _sys.stdout

    def cprint(self, obj):
        self._format(obj, self._stream, 0, 0, {}, 0)
        self._stream.write("\n")

    def cformat(self, obj):
        sio = _StringIO()
        self._format(obj, sio, 0, 0, {}, 0)
        return sio.getvalue()

    def _format(self, obj, stream, indent, allowance, context, level):
        level = level + 1
        objid = _id(obj)
        if objid in context:
            stream.write(_recursion(obj))
            self._recursive = True
            self._readable = False
            return
        rep = self._repr(obj, context, level - 1)
        typ = _type(obj)
        sepLines = _len(rep) > (self._width - 1 - indent - allowance)
        write = stream.write

        if self._depth and level > self._depth:
            write(rep)
            return

        r = getattr(typ, "__repr__", None)
        if (issubclass(typ, dict) and r is dict.__repr__) or \
                (issubclass(typ, OrderedDict) and r.__func__ is OrderedDict.__repr__.__func__):
            if isinstance(obj, OrderedDict):
                obj = dict(obj)
            if not obj:
                write('{ }')
                return
            else:
                write('{\n')
                length = _len(obj)
                if length:
                    context[objid] = 1
                    indent = indent + self._indent_per_level
                    items = _sorted(obj.items())
                    key, ent = items[0]
                    rep = self._repr(key, context, level)
                    # write(rep)
                    write('%s%s' % (' ' * indent, rep))
                    write(': ')
                    self._format(ent, stream, indent + _len(rep) + 2,
                                 allowance + 1, context, level)
                    if length > 1:
                        for key, ent in items[1:]:
                            rep = self._repr(key, context, level)
                            if sepLines:
                                write(',\n%s%s: ' % (' ' * indent, rep))
                            else:
                                write(', %s: ' % rep)
                            self._format(ent, stream, indent + _len(rep) + 2,
                                         allowance + 1, context, level)
                    indent = indent - self._indent_per_level
                    del context[objid]
                write('\n}')
                return

        if (issubclass(typ, list) and r is list.__repr__) or \
                (issubclass(typ, tuple) and r is tuple.__repr__) or \
                (issubclass(typ, set) and r is set.__repr__) or \
                (issubclass(typ, frozenset) and r is frozenset.__repr__):
            length = _len(obj)
            if issubclass(typ, list):
                write('[')
                endchar = ']'
            elif issubclass(typ, tuple):
                write('(')
                endchar = ')'
            else:
                if not length:
                    write(rep)
                    return
                write(typ.__name__)
                write('([')
                endchar = '])'
                indent += len(typ.__name__) + 1
                obj = _sorted(obj)
            if self._indent_per_level > 1 and sepLines:
                write((self._indent_per_level - 1) * ' ')
            if length:
                context[objid] = 1
                indent = indent + self._indent_per_level
                self._format(obj[0], stream, indent, allowance + 1,
                             context, level)
                if length > 1:
                    for ent in obj[1:]:
                        if sepLines:
                            write(',\n' + ' ' * indent)
                        else:
                            write(', ')
                        self._format(ent, stream, indent,
                                     allowance + 1, context, level)
                indent = indent - self._indent_per_level
                del context[objid]
            if issubclass(typ, tuple) and length == 1:
                write(',')
            write(endchar)
            return

        write(rep)

    def _repr(self, obj, context, level):
        repr, readable, recursive = self.format(obj, context.copy(), self._depth, level)
        if not readable:
            self._readable = False
        if recursive:
            self._recursive = True
        return repr

    def format(self, obj, context, maxlevels, level):
        """Format obj for a specific context, returning a string
        and flags indicating whether the representation is 'readable'
        and whether the obj represents a recursive construct.
        """
        return _safe_repr(obj, context, maxlevels, level)


def _safe_repr(obj, context, maxlevels, level):
    typ = _type(obj)
    if typ is str:
        if 'locale' not in _sys.modules:
            return repr(obj), True, False
        if "'" in obj and '"' not in obj:
            closure = '"'
            quotes = {'"': '\\"'}
        else:
            closure = "'"
            quotes = {"'": "\\'"}
        qget = quotes.get
        sio = _StringIO()
        write = sio.write
        for char in obj:
            if char.isalpha():
                write(char)
            else:
                write(qget(char, repr(char)[1:-1]))
        return ("%s%s%s" % (closure, sio.getvalue(), closure)), True, False

    r = getattr(typ, "__repr__", None)
    if issubclass(typ, dict) and r is dict.__repr__:
        if not obj:
            return "{}", True, False
        objid = _id(obj)
        if maxlevels and level >= maxlevels:
            return "{...}", False, objid in context
        if objid in context:
            return _recursion(obj), False, True
        context[objid] = 1
        readable = True
        recursive = False
        components = []
        append = components.append
        level += 1
        saferepr = _safe_repr
        for k, v in _sorted(obj.items()):
            krepr, kreadable, krecur = saferepr(k, context, maxlevels, level)
            vrepr, vreadable, vrecur = saferepr(v, context, maxlevels, level)
            append("%s: %s" % (krepr, vrepr))
            readable = readable and kreadable and vreadable
            if krecur or vrecur:
                recursive = True
        del context[objid]
        return "{%s}" % _commajoin(components), readable, recursive

    if (issubclass(typ, list) and r is list.__repr__) or \
            (issubclass(typ, tuple) and r is tuple.__repr__):
        if issubclass(typ, list):
            if not obj:
                return "[]", True, False
            format = "[%s]"
        elif _len(obj) == 1:
            format = "(%s,)"
        else:
            if not obj:
                return "()", True, False
            format = "(%s)"
        objid = _id(obj)
        if maxlevels and level >= maxlevels:
            return format % "...", False, objid in context
        if objid in context:
            return _recursion(obj), False, True
        context[objid] = 1
        readable = True
        recursive = False
        components = []
        append = components.append
        level += 1
        for o in obj:
            orepr, oreadable, orecur = _safe_repr(o, context, maxlevels, level)
            append(orepr)
            if not oreadable:
                readable = False
            if orecur:
                recursive = True
        del context[objid]
        return format % _commajoin(components), readable, recursive

    rep = repr(obj)
    return rep, (rep and not rep.startswith('<')), False


def _recursion(obj):
    return ("<Recursion on %s with id=%s>"
            % (_type(obj).__name__, _id(obj)))
